import { observe } from "./observe"
import Dep from "./observe/dep"
import Watcher, { nextTick } from "./observe/watcher"

export function initState (vm) {
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  }
  if (opts.computed) {
    initComputed(vm)
  }
  if (opts.watch) {
    initWatch(vm)
  }
}

function initData (vm) {
  let data = vm.$options.data
  // data是一个函数或者对象
  data = vm._data = typeof data === 'function'
  ? data.call(vm)
  : data || {}

  // 代理 vm._data 中的属性到 vue 实例, 支持通过 this.key 的方式访问
  for (const key in vm._data) {
    proxy(vm, '_data', key)
  }

  // 对数据进行劫持到核心是 Object.defineProperty
  observe(data)
}

// 数据代理
function proxy (vm, sourceKey, key) {
  Object.defineProperty(vm, key, {
    get () {
      return vm[sourceKey][key]
    },
    set (val) {
      vm[sourceKey][key] = val
    }
  })
}

function initComputed (vm) {
  const computed = vm.$options.computed
  const watchers = vm._computedWatchers = {}

  for (const key in computed) {
    const userDef = computed[key]

    const fn = typeof userDef === 'function' ? userDef : userDef.get
    // 添加lazy属性表示这是一个懒watcher，否则默认会立即执行
    // 将属性和watcher对应起来
    watchers[key] = new Watcher(vm, fn, { lazy: true })

    defineComputed(vm, key, userDef)
  }
}

function defineComputed (target, key, userDef) {
  const setter = userDef.set || (() => {})

  Object.defineProperty(target, key, {
    get: createComputedGetter(key),
    set: setter
  })
}

function createComputedGetter (key) {
  // 需要检测是否需要执行这个getter
  return function () {
    // console.log(this);
    const watcher = this._computedWatchers[key]
    if (watcher.dirty) {
      // 执行后dirty变为false，下次就不执行了
      watcher.evaluate()
    }

    if (Dep.target) { // 计算属性出栈后 还要渲染 watcher，让计算属性 watcher 里面的属性也去收集上一层watcher
      watcher.depend()
    }

    return watcher.value
  }
}

function initWatch (vm) {
  const watch = vm.$options.watch

  for (const key in watch) {
    const hanlder = watch[key] // 字符串 数组 函数 对象
    if (Array.isArray(hanlder)) {
      for (let i = 0; i < hanlder.length; i++) {
        createWatcher(vm, key, hanlder[i])
      }
    } else {
      createWatcher(vm, key, hanlder)
    }
  }
}

function createWatcher (vm, key, hanlder) {
  // 将字符串转为函数
  if (typeof hanlder === 'string') {
    hanlder = vm[key]
  }

  return vm.$watch(key, hanlder)
}

export function initStateMixin (Vue) {
  Vue.prototype.$nextTick = nextTick

  // watch 最终调用这个方法
  Vue.prototype.$watch = function (exprOrFn, cb) {
    // 标识这是一个用户watcher
    new Watcher(this, exprOrFn, { user: true }, cb)
  }
}